/*   The MIT License
 *   
 *   Carina Common
 *   Copyright (c) 2010-2011 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */
#include "carina/common/logging.hh"
#include "carina/common/exception.hh"

#include <iostream>

namespace Carina
{
string ConvertLogLevelToString(LoggingLevel level)
{
    switch(level)
    {
    case CE_LOG_ERROR: return "[ERROR]"; break;
    case CE_LOG_FATAL: return "[FATAL ERROR]"; break;
    case CE_LOG_WARNING: return "[WARNING]"; break;
    case CE_LOG_DEBUG: return "[DEBUG]"; break;
    case CE_LOG_INFO: return "[INFO]"; break;
    default: CE_ASSERT(false, "Unknown logging level");
    }
    return "[UNKNOWN]";
}

Log::Log()
    :   m_MinLoggingLevel(CE_LOG_INFO),
		m_CurrentLoggingLevel(CE_LOG_UNKNOWN),
		m_CurrentIndex(0)
{
}

Log::Log(const string& filename, LoggingLevel log_level)
    :   m_LogFile(filename.c_str(), std::ios::out | std::ios::trunc),
        m_MinLoggingLevel(log_level),
        m_CurrentLoggingLevel(CE_LOG_UNKNOWN),
        m_CurrentIndex(0)
{
    if(!m_LogFile.is_open())
        THROW_EXCEPTION(filename + ": invalid logging file");
}

Log::~Log()
{
    flush();
}

void Log::flush()
{
    flushCurrentMessage();
    auto& _stream = getOutputStream();
    while(m_CurrentIndex < m_LogMessages.size())
        printMessage(_stream, m_LogMessages[m_CurrentIndex++]);
    flushStream(_stream.flush());
}

void Log::setLogFile(const string& filename)
{
    m_LogFile.open(filename.c_str(), std::ios::out | std::ios::trunc);
    if(!m_LogFile.is_open())
        THROW_EXCEPTION(filename + ": invalid logging file");
}

void Log::setMinLoggingLevel(LoggingLevel log_level)
{
    m_MinLoggingLevel = log_level;
}

LoggingLevel Log::getMinLoggingLevel()
{
    return m_MinLoggingLevel;
}

Log& Log::logStream(LoggingLevel log_level)
{
    m_CurrentLoggingLevel = log_level;
    if(m_CurrentLoggingLevel >= m_MinLoggingLevel)
    {
        flushCurrentMessage();
        m_Timer.update();
        m_Timestamp = m_Timer.getTime();
        flush(); // TODO: Just for console applications where there is no rendering loop or other stuff that could be 
                 //       slowed down by IO. It should be done on separate thread or something.
    }
    return *this;
}

void Log::printMessage(std::ostream& _stream, const LogMessage& msg)
{
    nanosec m = msg.timestamp / 60000000000LL;
    nanosec s = (msg.timestamp % 60000000000LL) / 1000000000LL;
    nanosec ns = (msg.timestamp % 1000000000LL);
    _stream << m << ":" << s << "." << ns << ": " << ConvertLogLevelToString(msg.level) << ": " << msg.message << "\n";
}

std::ostream& Log::getOutputStream()
{
    if(m_LogFile.is_open())
        return m_LogFile;
	// On Windows it uses an intermediate stream to output to the Output Window.
#ifdef _MSC_VER
	return m_MessageBuffer;
#else
    else if(m_CurrentLoggingLevel == CE_LOG_ERROR ||
            m_CurrentLoggingLevel == CE_LOG_FATAL ||
            m_CurrentLoggingLevel == CE_LOG_WARNING)
        return std::cerr;
    return std::cout;
#endif
}

void Log::flushStream(std::ostream& os)
{
	// It flushes the data to the Output Window in the case of Windows; otherwise, it
	// works in the usual fashion.
#ifdef _MSC_VER
	if(!m_LogFile.is_open())
	{
		auto str = m_MessageBuffer.str();
		OutputDebugString(str.c_str());
	}
#else
	os.flush();
#endif
}

void Log::logMessage(LoggingLevel log_level, const string& msg)
{
    logStream(log_level) << msg << std::endl;
}

string Log::readLog()
{
    flushCurrentMessage();
    std::stringstream ss;
    for(auto& msg : m_LogMessages)
        printMessage(ss, msg);
    return ss.str();
}

void Log::flushCurrentMessage()
{
    auto msg = m_MessageBuffer.str();
    if(!msg.empty())
    {
        m_LogMessages.push_back(LogMessage(m_CurrentLoggingLevel, m_Timestamp, msg));
        m_MessageBuffer.str("");
    }
}
}
